
// <copyright file="SearchableSortableBindingList.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The searchable sortable binding list.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Collections.Generic
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;

    using SubhadraSolutions.Sharp.Utils.Reflection;

    /// <summary>
    /// The searchable sortable binding list.
    /// </summary>
    /// <typeparam name="T">
    /// </typeparam>
    public class SearchableSortableBindingList<T> : BindingList<T>
    {
        #region Fields

        /// <summary>
        /// The current.
        /// </summary>
        private T _current;

        /// <summary>
        /// The is sorted.
        /// </summary>
        private bool _isSorted;

        /// <summary>
        /// The sort direction.
        /// </summary>
        private ListSortDirection _sortDirection;

        /// <summary>
        /// The sort property.
        /// </summary>
        private PropertyDescriptor _sortProperty;

        #endregion Fields

        #region Public Properties

        /// <summary>
        /// Gets or sets the current.
        /// </summary>
        /// <value>
        /// The current.
        /// </value>
        /// <exception cref="System.IO.InvalidDataException">This item does n't exist in the collection</exception>
        /// <exception cref="InvalidDataException"></exception>
        public T Current
        {
            get
            {
                return this._current;
            }

            set
            {
                if (this.Contains(value))
                {
                    this._current = value;
                }
                else
                {
                    throw new InvalidDataException("This item does n't exist in the collection");
                }
            }
        }

        /// <summary>
        /// Gets the sort direction.
        /// </summary>
        public ListSortDirection SortDirection
        {
            get
            {
                return this._sortDirection;
            }
        }

        /// <summary>
        /// Gets the <see cref="T:System.ComponentModel.PropertyDescriptor" /> that is being used for sorting.
        /// </summary>
        /// <returns>The <see cref="T:System.ComponentModel.PropertyDescriptor" /> that is being used for sorting.</returns>
        public PropertyDescriptor SortProperty
        {
            get
            {
                return this._sortProperty;
            }
        }

        #endregion Public Properties

        #region Properties

        /// <summary>
        /// Gets a value indicating whether is sorted core.
        /// </summary>
        protected override bool IsSortedCore
        {
            get
            {
                return this._isSorted;
            }
        }

        /// <summary>
        /// Gets the sort direction core.
        /// </summary>
        /// <value>
        /// The sort direction core.
        /// </value>
        protected override ListSortDirection SortDirectionCore
        {
            get
            {
                return this._sortDirection;
            }
        }

        /// <summary>
        /// Gets the sort property core.
        /// </summary>
        /// <value>
        /// The sort property core.
        /// </value>
        protected override PropertyDescriptor SortPropertyCore
        {
            get
            {
                return this._sortProperty;
            }
        }

        /// <summary>
        /// Gets a value indicating whether supports searching core.
        /// </summary>
        protected override bool SupportsSearchingCore
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a value indicating whether supports sorting core.
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get
            {
                return true;
            }
        }

        #endregion Properties

        #region Public Methods and Operators

        /// <summary>
        /// The apply sort.
        /// </summary>
        /// <param name="property">
        /// The property.
        /// </param>
        /// <param name="direction">
        /// The direction.
        /// </param>
        public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
        {
            this.ApplySortCore(property, direction);
        }

        /// <summary>
        /// The load.
        /// </summary>
        /// <param name="filename">
        /// The filename.
        /// </param>
        public void Load(string filename)
        {
            this.ClearItems();

            if (File.Exists(filename))
            {
                var formatter = new BinaryFormatter();
                using (var stream = new FileStream(filename, FileMode.Open))
                {
                    // Deserialize data list items
                    ((List<T>)this.Items).AddRange((IEnumerable<T>)formatter.Deserialize(stream));
                }
            }

            // Let bound controls know they should refresh their views
            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        /// <summary>
        /// The load.
        /// </summary>
        /// <param name="items">
        /// The items.
        /// </param>
        public void Load(IList<T> items)
        {
            foreach (T item in items)
            {
                this.Add(item);
            }
        }

        /// <summary>
        /// The save.
        /// </summary>
        /// <param name="filename">
        /// The filename.
        /// </param>
        public void Save(string filename)
        {
            var formatter = new BinaryFormatter();
            using (var stream = new FileStream(filename, FileMode.Create))
            {
                // Serialize data list items
                formatter.Serialize(stream, this.Items);
            }
        }

        #endregion Public Methods and Operators

        #region Methods

        /// <summary>
        /// The apply sort core.
        /// </summary>
        /// <param name="property">
        /// The property.
        /// </param>
        /// <param name="direction">
        /// The direction.
        /// </param>
        protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
        {
            // Get list to sort
            // Note: this.Items is a non-sortable ICollection<T>
            var items = this.Items as List<T>;

            // Apply and set the sort, if items to sort
            if (items != null)
            {
                var comparer = DynamicComparerHelper<T>.GetComparerForProperty(property.Name);
                if (comparer == null)
                {
                    return;
                }
                
                if (direction == ListSortDirection.Descending)
                {
                    comparer = new InverseComparer<T>(comparer);
                }

                items.Sort(comparer);
                this._isSorted = true;
            }
            else
            {
                this._isSorted = false;
            }

            this._sortProperty = property;
            this._sortDirection = direction;

            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        /// <summary>
        /// The find core.
        /// </summary>
        /// <param name="property">
        /// The property.
        /// </param>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        protected override int FindCore(PropertyDescriptor property, object key)
        {
            // Specify search columns
            if (property == null)
            {
                return -1;
            }

            // Get list to search
            IList<T> items = this.Items;

            // Traverse list for value
            foreach (T item in items)
            {
                // Test column search value
                var value = (string)property.GetValue(item);

                // If value is the search value, return the
                // index of the data item
                if ((string)key == value)
                {
                    return this.IndexOf(item);
                }
            }

            return -1;
        }

        /// <summary>
        /// The remove sort core.
        /// </summary>
        protected override void RemoveSortCore()
        {
            this._isSorted = false;
            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        #endregion Methods
    }
}